package cn.gxufe.reliable.message.admin.lock;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @author yingjie.rong
 **/
public class ClusterElectionManager {

    public static final HystrixCommandGroupKey MYSQL_GROUP_KEY =  HystrixCommandGroupKey.Factory.asKey("mysql");
    public static final HystrixCommandKey MYSQL_COMMAND_KEY =  HystrixCommandKey.Factory.asKey("mysqlCommand");
    public static final  HystrixCommandProperties.Setter MYSQL_SETTER =
            HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds(10000)
                    .withCircuitBreakerRequestVolumeThreshold(5);

    protected static Logger logger = LoggerFactory.getLogger(ClusterElectionManager.class);
    private static volatile boolean master = false;
    private static String LOCK_KEY = "cluster_election_key";
    private final  long expire = 3 * 1000;
    private static InstanceInfo instanceInfo = new InstanceInfo(UUID.randomUUID().toString().replace("-",""));

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private boolean open;
    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;

    public ClusterElectionManager( boolean open ){
       this.open = open;
    }


    @PreDestroy
    public void shutDown(){
        if(scheduledThreadPoolExecutor != null) {
            scheduledThreadPoolExecutor.shutdown();
        }
    }

    @PostConstruct
    public void afterInit(){
        if(open) {
            scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
            scheduledThreadPoolExecutor.scheduleAtFixedRate( ()-> clusterElection() ,5, 1,TimeUnit.SECONDS);
            mysqlHystrixCommandFallbackFunction((Void v) -> {
                try {
                    jdbcTemplate.batchUpdate("insert into `lock` (lock_name,ins_id,expire_time,host) values(?,?,?,?)", new ArrayList<Object[]>() {
                        {
                            add(new Object[]{LOCK_KEY, instanceInfo.getUuid(), System.currentTimeMillis(), instanceInfo.getHostname()});
                        }
                    });
                    return true;
                } catch (Throwable t) {
                    logger.error(t.getMessage());
                    return false;
                }
            }, (Throwable t) -> false).execute();
        }else {
            master = true;
        }
    }

    private void clusterElection(){
        Boolean update = mysqlHystrixCommandFallbackFunction((Void v) -> {
            int[] batchUpdate = jdbcTemplate.batchUpdate("update `lock` set expire_time = ? where ins_id =? and lock_name =?", new ArrayList<Object[]>() {
                {
                    add(new Object[]{System.currentTimeMillis() + expire, instanceInfo.getUuid(), LOCK_KEY});
                }
            });
            if (batchUpdate[0] == 1) {
                return true;
            }
            batchUpdate = jdbcTemplate.batchUpdate("update `lock` set expire_time = ?,ins_id = ? ,host = ?" +
                    "where expire_time < ? and ins_id != ? and lock_name =?", new ArrayList<Object[]>() {
                {
                    add(new Object[]{System.currentTimeMillis() + expire, instanceInfo.getUuid(), instanceInfo.getHostname(), System.currentTimeMillis(), instanceInfo.getUuid(), LOCK_KEY});
                }
            });
            if (batchUpdate[0] == 1) {
                return true;
            }
            return false;
        }, (Throwable t) -> {
            logger.error(t.getMessage());
            return false;
        } ).execute();
        if(update){
            master = true;
        } else {
            master = false;
        }
    }

    public static boolean isMaster(){
        return master;
    }

    public static InstanceInfo getInstanceInfo(){
        return instanceInfo;
    }

    private static HystrixCommand.Setter mysqlSetter(){
        return HystrixCommand.Setter.withGroupKey(MYSQL_GROUP_KEY).andCommandKey(MYSQL_COMMAND_KEY).andCommandPropertiesDefaults(MYSQL_SETTER);
    }


    private static <T,R> HystrixCommand<R> mysqlHystrixCommandFallbackFunction(final Function<T,R> function, final Function<Throwable,R> defaultFunction){
        return new HystrixCommand<R>(mysqlSetter()) {
            @Override
            protected R run(){
                return function.apply(null);
            }
            @Override
            protected R getFallback(){
                return defaultFunction.apply(this.getExecutionException());
            }
        };
    }
}
